package com.alogic.xscript.ldap;

import com.alogic.xscript.AbstractLogiclet;
import com.alogic.xscript.ExecuteWatcher;
import com.alogic.xscript.Logiclet;
import com.alogic.xscript.LogicletContext;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.plugins.Segment;
import com.anysoft.util.Properties;
import com.anysoft.util.PropertiesConstants;
import org.apache.commons.lang3.StringUtils;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Hashtable;

/**
 * 创建一个LDAP连接,并绑定到指定的DN
 */
public class LDAPConnection extends Segment {

    /**
     * 上下文id
     */
    protected String cid = "$ldap";

    /**
     * ldap服务器的URL,形式为:ldap://ip:port
     */
    protected String $url;

    /**
     * 待绑定的dn
     */
    protected String $dn;

    /**
     * 密码
     */
    protected String $pwd;

    /**
     * 结果id
     */
    protected String id;

    private static ThreadLocal<Hashtable<String,String>> envFactory = new ThreadLocal<Hashtable<String,String>>(){
        public Hashtable<String,String> initialValue() {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            return env;
        }
    };

    public LDAPConnection(String tag, Logiclet p) {
        super(tag, p);
        registerModule("ldap-add",LDAPAdd.class);
        registerModule("ldap-del",LDAPDel.class);
        registerModule("ldap-update",LDAPAdd.class);
        registerModule("ldap-update-pwd",LDAPUpdatePassword.class);
        registerModule("ldap-scan",LDAPScan.class);
    }

    @Override
    public void configure(Properties p){
        super.configure(p);
        id = PropertiesConstants.getString(p,"id","",true);
        cid = PropertiesConstants.getString(p,"cid",cid,true);
        $url = PropertiesConstants.getRaw(p,"url","");
        $dn = PropertiesConstants.getRaw(p,"dn","");
        $pwd = PropertiesConstants.getRaw(p,"pwd","");
    }

    @Override
    protected void onExecute(XsObject root, XsObject current, LogicletContext ctx,
                             ExecuteWatcher watcher) {
        String url = PropertiesConstants.transform(ctx,$url,"");
        String dn = PropertiesConstants.transform(ctx,$dn,"");
        String password = PropertiesConstants.transform(ctx,$pwd,"");
        if (StringUtils.isEmpty(url) || StringUtils.isEmpty(dn)){
            log(String.format("Can not bind ldap server.url=%s,dn=%s",url,dn),LOG_ERROR,ctx);
            return;
        }

        DirContext ldapContext = null;
        try{
            Hashtable<String, String> env = envFactory.get();
            env.put(Context.PROVIDER_URL,url);
            env.put(Context.SECURITY_PRINCIPAL, dn);
            env.put(Context.SECURITY_CREDENTIALS, password);

            ldapContext = new InitialDirContext(env);

            ctx.setObject(cid,ldapContext);
            if (StringUtils.isNotEmpty(id)){
                ctx.SetValue(id,"true");
            }

            super.onExecute(root,current,ctx,watcher);
        }catch (NamingException ex){
            log("Can not bind ldap server:" + ex.toString(),LOG_ERROR,ctx);
            if (StringUtils.isNotEmpty(id)){
                ctx.SetValue(id,ex.getClass().getName());
                ctx.SetValue(id + ".reason",ex.toString(true));
            }
        }finally{
            ctx.removeObject(cid);
            if (ldapContext != null){
                try {
                    ldapContext.close();
                }catch (Exception ex){
                    log("Something wrong to close ldap context:" + ex.getMessage(),LOG_ERROR,ctx);
                }
            }
        }
    }

    /**
     * 基于LDAP的操作
     */
    public abstract static class Operation extends AbstractLogiclet {
        /**
         * 上下文id
         */
        protected String pid = "$ldap";
        public Operation(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public  void configure(Properties p){
            super.configure(p);
            pid = PropertiesConstants.getString(p,"pid",pid,true);
        }

        @Override
        protected void onExecute(XsObject root, XsObject current, LogicletContext ctx,
                                 ExecuteWatcher watcher) {
            DirContext dirContext = ctx.getObject(pid);
            if (dirContext == null){
                log("Can not find dir context object,check your script,pid=" + pid,LOG_ERROR,ctx);
                return ;
            }

            onExecute(dirContext,root,current,ctx,watcher);
        }
        protected abstract void onExecute(DirContext dirContext, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher);
    }
}
